#include "OpenglAppFramework/inc/OpenglApp.h"
#include "OpenglAppFramework/inc/Config.h"
#include "OpenglAppFramework/inc/OpenglEnv.h"
#include "OpenglAppFramework/inc/SceneConfig.h"
#include "OpenglAppFramework/inc/PlugIn/Mesh.h"
#include "OpenglAppFramework/inc/PlugIn/Camera.h"
#include "OpenglAppFramework/inc/PlugIn/LightSource.h"
#include "OpenglAppFramework/inc/Utils.h"
#include "json/inc/json.hpp"
#include <iostream>
#include <fstream>

static float debug_vtx[] = {
    // x y      u v
    -1, -1,     0, 0,
    -1, 1,      0, 1,
    1, 1,       1, 1,
    1, 1,       1, 1,
    1, -1,      1, 0,
    -1, -1,     0, 0
};

// #include <cstdio>
// void GLAPIENTRY
// MessageCallback( GLenum source,
//                  GLenum type,
//                  GLuint id,
//                  GLenum severity,
//                  GLsizei length,
//                  const GLchar* message,
//                  const void* userParam )
// {
//   fprintf( stderr, "GL CALLBACK: type = 0x%x, severity = 0x%x, message = %s\n", type, severity, message );
// }

void framebufferSizeCallback(GLFWwindow*, int width, int height) {
    glViewport(0, 0, width, height);
}

OpenglApp::OpenglApp() {
    m_is_show_fps = false;
    m_frame_cnt = 0;
    m_last_check_time = glfwGetTime();
    m_last_frame_time = m_last_check_time;
    m_frame_debug_vao_id = 0;
    m_frame_debug_vbo_id = 0;
}

OpenglApp::~OpenglApp() {}

int OpenglApp::init(const std::string& title, const std::string& scene_file) {
    // 读取场景文件
    std::cout<<"load scene file ..." << std::endl;
    std::ifstream ifs(scene_file);
    nlohmann::json root = nlohmann::json::parse(ifs, nullptr, true, true);
    addDefaultValue(root);

    for(auto& it : root["game_objects"].items()) {
        std::cout<<"    create game_object: " << it.key() << std::endl;
        GameObject* obj = GameObject::CreateGameObjectByJson(&m_scene, it.key(), it.value());
        if(obj == nullptr) {
            std::cout << "create game object failed: " << it.key() << std::endl;
            return -1;
        }
        m_scene.setGameObject(obj);
    }
    std::cout<<"create scene ok"<<std::endl;

    // OPENGL与操作系统相关
    OpenglEnv& env = m_scene.env();
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // GLFW创建窗口
    env.window = glfwCreateWindow(OpenglAppConfig::width, OpenglAppConfig::height, title.c_str(), NULL, NULL);
    if (env.window == NULL) {
        std::cerr << "Failed to create GLFW window." << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(env.window);
    glfwSetFramebufferSizeCallback(env.window, framebufferSizeCallback);

    // GLAD加载所有OpenGL函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwSetInputMode(env.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    std::cout<<"create opengl ok"<<std::endl;

    glEnable(GL_DEPTH_TEST);
    // glEnable              ( GL_DEBUG_OUTPUT );
    // glDebugMessageCallback( MessageCallback, 0 );CHECK_OPENGL(__FILE__,__LINE__);

    glGenVertexArrays(1, &m_frame_debug_vao_id);
    glGenBuffers(1, &m_frame_debug_vbo_id);
    glBindVertexArray(m_frame_debug_vao_id);
    glBindBuffer(GL_ARRAY_BUFFER, m_frame_debug_vbo_id);
    glBufferData(GL_ARRAY_BUFFER, sizeof(debug_vtx), debug_vtx, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), reinterpret_cast<void*>(0));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), reinterpret_cast<void*>(2*sizeof(float)));

    std::cout<<"create frame debug vertex ok"<<std::endl;
    return 0;
}

int OpenglApp::start() {
    std::string frm_dbg_vert = OpenglAppConfig::shader_dir + "/frame_buf_debug.vert";
    std::string frm_dbg_frag = OpenglAppConfig::shader_dir + "/frame_buf_debug.frag";
    if(m_frame_debug_shader.createAndBuild(frm_dbg_vert, frm_dbg_frag)) {
        return -1;
    }
    std::string vert_shader = OpenglAppConfig::shader_dir + "/default.vert";
    std::string frag_shader = OpenglAppConfig::shader_dir + "/default.frag";
    if(m_default_shader.createAndBuild(vert_shader, frag_shader) != 0) {
        return -1;
    }
    m_default_shader.use();

    std::cout<<"create default shader ok"<<std::endl;
    m_default_shader.setVec2("use_nouse_instancing", glm::vec2(0.0, 1.0));CHECK_OPENGL(__FILE__,__LINE__);

    for(auto kv : m_scene.objects()) {
        if(kv.second->initAllPlugIns() != 0) {
            return -1;
        }
        kv.second->updateModelMat();
    }CHECK_OPENGL(__FILE__,__LINE__);
    std::cout<<"init plugin ok"<<std::endl;
    m_is_show_fps = true;


    OpenglEnv& env = m_scene.env();CHECK_OPENGL(__FILE__,__LINE__);
    while (!glfwWindowShouldClose(env.window)) {
        // ESC退出
        if(glfwGetKey(env.window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
            glfwSetWindowShouldClose(env.window, true);
        }
        // 计算frame的delta_time
        m_cur_time = glfwGetTime();
        env.delta_time = m_cur_time - m_last_frame_time;
        m_last_frame_time = m_cur_time;

        handleInput();
        mainLoop();

        glfwSwapBuffers(env.window);
        glfwPollEvents();
        mainLoopExtrel();
        calculateFps();
    }
    afterMainLoop();

    return 0;
}

void OpenglApp::destroy() {
    OpenglEnv& env = m_scene.env();
    if(env.window != nullptr) {
        glfwTerminate();
    }
}

void OpenglApp::handleInput() {
    OpenglEnv& env = m_scene.env();
    env.key_q = glfwGetKey(env.window, GLFW_KEY_Q) == GLFW_PRESS ? true : false;
    env.key_w = glfwGetKey(env.window, GLFW_KEY_W) == GLFW_PRESS ? true : false;
    env.key_e = glfwGetKey(env.window, GLFW_KEY_E) == GLFW_PRESS ? true : false;
    env.key_a = glfwGetKey(env.window, GLFW_KEY_A) == GLFW_PRESS ? true : false;
    env.key_s = glfwGetKey(env.window, GLFW_KEY_S) == GLFW_PRESS ? true : false;
    env.key_d = glfwGetKey(env.window, GLFW_KEY_D) == GLFW_PRESS ? true : false;
}

void OpenglApp::mainLoop() {CHECK_OPENGL(__FILE__,__LINE__);
    glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 执行所有routine
    for(auto kv : m_scene.objects()) {
        for(auto it : kv.second->plugIns()) {
            it.second->routine();
        }
    }
    // 统一更新所有obj的model_mat
    for(auto kv : m_scene.objects()) {
        kv.second->updateModelMat();
    }

    // 渲染shadow_map
    for(auto light_kv : m_scene.shadowLightObjects()) {
        LightSource* light = light_kv.second->getLightSource();
        Shader* shadow_shader = light->getShadowShader();
        shadow_shader->use();CHECK_OPENGL(__FILE__,__LINE__);
        light_kv.second->getLightSource()->uploadShadowShaderData(shadow_shader);CHECK_OPENGL(__FILE__,__LINE__);

        glBindFramebuffer(GL_FRAMEBUFFER, light->getFrameBufferId());
        glClear(GL_DEPTH_BUFFER_BIT);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, light->getShadowTextureId());CHECK_OPENGL(__FILE__,__LINE__);
        // 渲染普通物体的深度
        for(auto normal_kv : m_scene.normalObjects()) {
            if(normal_kv.second->isCastShadow() == false) {
                continue;
            }
            normal_kv.second->uploadModelMat(shadow_shader);CHECK_OPENGL(__FILE__,__LINE__);
            normal_kv.second->render();
        }
        // 渲染实例化物体的深度
        if(m_scene.hasInstancing()) {
            shadow_shader->setVec2("use_nouse_instancing", glm::vec2(1, 0));CHECK_OPENGL(__FILE__,__LINE__);
            for(auto inst_kv : m_scene.instancingObjects()) {
                if(inst_kv.second->isCastShadow() == false) {
                    continue;
                }
                inst_kv.second->render();
            }
            shadow_shader->setVec2("use_nouse_instancing", glm::vec2(0, 1));
        }
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    // m_frame_debug_shader.use();
    // m_frame_debug_shader.setInt("frame_buffer_texture_sampler",0);
    // // glLineWidth(1.0f);
    // // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    // glBindVertexArray(m_frame_debug_vao_id);
    // glDrawArrays(GL_TRIANGLES, 0, 6);
    // return ;

    m_default_shader.use();
    m_default_shader.setInt("light.shdadow_map_unit_id", 0);CHECK_OPENGL(__FILE__,__LINE__);
    // 更新光源和相机
    for(auto kv : m_scene.lightSourceObjects()) {
        GameObject* obj = kv.second;
        for(auto& it : obj->plugIns()) {
            it.second->uploadShaderData(&m_default_shader);
        }
    }
    for(auto kv : m_scene.cameraObjects()) {
        GameObject* obj = kv.second;
        for(auto& it : obj->plugIns()) {
            it.second->uploadShaderData(&m_default_shader);
        }
    }
    // 先渲染所有非实例化的物体
    for(auto kv : m_scene.normalObjects()) {
        // 上传所有plugin的数据
        GameObject* obj = kv.second;
        for(auto& plugin_kv : obj->plugIns()) {
            PlugIn* plugin = plugin_kv.second;
            if(plugin->isEnable() == false) {
                continue;
                // !!! 以后这里应该用ActivePluginIterator代替
            }
            plugin->uploadShaderData(&m_default_shader);
        }
        obj->uploadModelMat(&m_default_shader);
        obj->textureManger().uploadShaderData(&m_default_shader);
        obj->render();
    }
    // 渲染所有实例化的物体
    if(m_scene.hasInstancing()) {
        m_default_shader.setVec2("use_nouse_instancing", glm::vec2(1, 0));
        for(auto kv : m_scene.instancingObjects()) {
            // 上传所有plugin的数据
            GameObject* obj = kv.second;
            for(auto& plugin_kv : obj->plugIns()) {
                PlugIn* plugin = plugin_kv.second;
                if(plugin->isEnable() == false) {
                    continue;
                    // !!! 以后这里应该用ActivePluginIterator代替
                }
                plugin->uploadShaderData(&m_default_shader);
            }
            obj->uploadModelMat(&m_default_shader);
            obj->textureManger().uploadShaderData(&m_default_shader);
            obj->render();
        }
        m_default_shader.setVec2("use_nouse_instancing", glm::vec2(0, 1));
    }
}

void OpenglApp::mainLoopExtrel() {}

void OpenglApp::afterMainLoop() {}

void OpenglApp::setIsShowFps(bool flag) {
    m_is_show_fps = flag;
}

Scene& OpenglApp::manager() {
    return m_scene;
}

#define FPS_CHECK_INTERVAL  (2.0)   // 秒
void OpenglApp::calculateFps() {
    if(m_is_show_fps == false) {
        return;
    }

    m_frame_cnt += 1;
    double elapse = m_cur_time - m_last_check_time;
    if(elapse > FPS_CHECK_INTERVAL) {
        m_last_check_time = m_cur_time;
        double fps = m_frame_cnt / elapse;
        m_frame_cnt = 0;
        
        std::cout << "fps: " << fps << std::endl;
    }
}
